#include <cstring>
#include <stdio.h> 
#include <iostream> 
#include <sstream>


#include "linux_keys.h"
#include "console_key_reader.h"
#include "editor.h"
#include "util.h"

#define KEYCODE_CTRL_Q 17
#define KEYCODE_CTRL_S 19
#define KEYCODE_UP 19
#define KEYCODE_DOWN 19
#define KEYCODE_LEFT 19
#define KEYCODE_RIGHT 19
#define VERSION "0.0.1"

#define QUIT_THRESHOLD 3


//uncomment next row to print keycode instead of document
//#define OUTPUT_KEYCODE


using namespace std;

SearchDirection Editor::s_direction = SearchDirection::Forward;

Editor::Editor()
{
    should_quit = false;
    _cursor_position = Position{0,0};
    string initial_status = "HELP: Ctrl-F = find | Ctrl-S = save | Ctrl-Q = quit";
    _status_message = new StatusMessage(initial_status);
    _quit_times = QUIT_THRESHOLD;
    _highlighted_word = "";
}

Editor::Editor(char* fileName)
{
    should_quit = false;
    _cursor_position = Position{0,0};
    string initial_status = "HELP: Ctrl-F = find | Ctrl-S = save | Ctrl-Q = quit";
    int openRtn = _document.Open(fileName);
    string fn = fileName;
    if (openRtn<0){
        initial_status = "ERR: Could not open file :"+fn;
    }
    _status_message = new StatusMessage(initial_status);
    _highlighted_word = "";
}

Editor::~Editor(){
    should_quit = false;
    delete _status_message;
}


void Editor::run(){
    while(true){
       refresh_screen();
       if (should_quit){
           break;
       }
       if (!process_keypress()){
           cout<< "exit with read key error"<<endl;
           return;
       }
    }
}


void Editor::die(){
    _terminal.ClearScreen();
    exit(0);
}


bool Editor::process_keypress(){
    KeyCode keycode = _reader.ReadKeyCode();
#ifdef OUTPUT_KEYCODE
    cout <<"{" <<  "keycode.Code:"  <<keycode.Code<<","<<"keycode.Type:"<<(int)keycode.Type<<"}" << endl;
#endif
    switch(keycode.Type){
        case KeyType::Ctrl:{
            if (keycode.Code=='q'){
                if (this->_quit_times > 0 && this->_document.IsDirty()){
                    stringstream ss;
                    ss<<"WARNING! File has unsaved changes. Press Ctrl-Q "<<this->_quit_times<<"  more times to quit.";
                    this->_status_message->SetMessage(ss.str());
                    this->_quit_times -= 1;
                    return true;
                }
                this->should_quit = true;
            } else if (keycode.Code == 's'){
                this->Save();
            } else if (keycode.Code == 'f'){
                this->search();
            }
        } 
        break;
        case KeyType::Up:
        case KeyType::Down:
        case KeyType::Left:
        case KeyType::Right:
        case KeyType::PageUp:
        case KeyType::PageDown:
        case KeyType::End:
        case KeyType::Home:
        {
            move_cursor(keycode);
        } 
        break;
        case KeyType::Char:{
            _document.Insert(_cursor_position,(char)keycode.Code);
            move_cursor(KeyCode{KeyType::Right,keycode.Code});

        }break;
        case KeyType::Delete:{
            _document.Delete(_cursor_position);
        }break;
        case KeyType::Backspace:{
            if (this->_cursor_position.x > 0 || this->_cursor_position.y > 0){
                move_cursor(KeyCode{KeyType::Left,keycode.Code});
                _document.Delete(_cursor_position);
            }
        }break;
        default:{
            cout << keycode.Code<< endl;
        }
        break;
    }
    Scroll();
    if (this->_quit_times < QUIT_THRESHOLD){
        this->_quit_times  = QUIT_THRESHOLD;
        this->_status_message->SetMessage("");
    }
    return true;
}



void Editor::Save(){
    if (this->_document.FileName==NULL){
        string newName = this->Prompt("Save as: ",Editor::saveAsCallback);
        if (newName.length()==0){
            this->_status_message->SetMessage("Save aborted.");
            return;
        }
        this->_document.FileName = new string(newName);
    }
    if (this->_document.Save()==0){
        _status_message->SetMessage("File saved successfully.");
    } else {
        _status_message->SetMessage("Error writing file!");
    }
}



void Editor::saveAsCallback(Editor* editor,KeyCode key,string* str){

}



void Editor::searchCallback(Editor* editor,KeyCode key,string* str){
    bool moved = false;
    if (key.Type == KeyType::Right || key.Type == KeyType::Down){
        Editor::s_direction = SearchDirection::Forward;
        editor->move_cursor(KeyCode{KeyType::Right,0});
        moved = true;
    } else if (key.Type == KeyType::Left || key.Type == KeyType::Up){
        Editor::s_direction = SearchDirection::Backward;
    } else {
        Editor::s_direction = SearchDirection::Forward;
    }
    Position pos = editor->_document.Find(*str,editor->_cursor_position,Editor::s_direction);
    if (pos.x >=0 && pos.y>=0){
        editor->_cursor_position = pos;
        editor->Scroll();
    } else if (moved){
        editor->move_cursor(KeyCode{KeyType::Left,0});
    }
    editor->_highlighted_word = *str;
}



string Editor::Prompt(const char* prompt,void (*callback)(Editor* editor,KeyCode key,string* str)){
    string message(prompt);
    string clearmessage("");
    string result("");
    while(1){
        this->_status_message->SetMessage(message);
        this->refresh_screen();
        KeyCode key = _reader.ReadKeyCode();
        if (key.Type == KeyType::Backspace){
            if (result.length()!=0){
                result = result.substr(0,result.length()-1);
                message = message.substr(0,message.length()-1);
            }
        } else if (key.Type == KeyType::Esc){
            result.erase(0,result.length());
            break;
        } else if (key.Type == KeyType::Char){
            if (key.Code == '\n'){
                break;
            }  
            result.push_back(key.Code);
            message.push_back(key.Code);
        }
        (*callback)(this,key,&result);
    }
    this->_status_message->SetMessage(clearmessage);
    return result;
}



void Editor::Scroll(){
    int x = _cursor_position.x;
    int y = _cursor_position.y;
    int width = _terminal.Size().Width;
    int height = _terminal.Size().Height;

    if (y < _offset.y){
        _offset.y = y;
    } else if (y >= Util::SaturatingAdd(_offset.y,width) ){
        _offset.y =Util::SaturatingAdd(Util::SaturatingSub(y,width),1);
    }

    if (x < _offset.x){
        _offset.x = x;
    } else if (x>= Util::SaturatingAdd(_offset.x , height)){
        _offset.x = Util::SaturatingAdd(Util::SaturatingSub(x,height),1);
    }
}


void Editor::refresh_screen(){

#ifdef OUTPUT_KEYCODE
    return;  
#endif
    _terminal.CursorHide();
    _terminal.CursorPosition(Position{0,0});
    if (should_quit){
        _terminal.ClearScreen();
        cout << "Good bye."<<endl;
    } else {
        this->_document.Highlight(this->_highlighted_word,this->_offset.x + this->_terminal.Size().Height);
        draw_rows();
        draw_status_bar();
        draw_message_bar();
        _terminal.CursorPosition(Position{
            (int)Util::SaturatingSub(this->_cursor_position.x,this->_offset.x),
            (int)Util::SaturatingSub(this->_cursor_position.y,this->_offset.y)
        });
    }
    _terminal.CursorShow();
    _terminal.Flush();
}

void Editor::draw_status_bar(){
    int width = _terminal.Size().Width;

    _terminal.SetStyle(ColorStyle::Invert);
    stringstream s_message;
    if (_document.FileName ==NULL){
        s_message<<"[No Name]";
    } else {
        s_message<<*(_document.FileName);
    }
    s_message << " - ";
    s_message << to_string( _document.Len());
    s_message << " lines   ";

    stringstream s_line_indicator;
    s_line_indicator << this->_document.GetFileType() << " | ";
    s_line_indicator<< to_string(this->_cursor_position.x+1) << "/" << to_string(_document.Len());
    if (_document.IsDirty()){
        s_line_indicator << "(modified)";
    }

    int messageLen = s_message.str().length()+s_line_indicator.str().length();
    if (width>messageLen){
        for(int i=0;i<width - messageLen;i++){
            s_line_indicator << " ";
        }
    }
    cout<<s_message.str()<<s_line_indicator.str()<<endl;
    _terminal.ResetStyle();
}


void Editor::draw_message_bar(){
    _terminal.ClearCurrentLine();
    if ((clock() - _status_message->Time) < 500){
        cout <<_status_message->Text;
        cout.flush();
    }
}


void Editor::draw_rows(){
    int height = _terminal.Size().Height;
    Row* p_row;
    for (int i=0;i<height ;i++){
        _terminal.ClearCurrentLine();
        p_row = _document.GetRowByIndex(i+this->_offset.x);
        if (p_row != NULL){
            draw_row(p_row);
        } else if (_document.IsEmpty() && i == height/3){
            draw_welcome_message();
        } else {
            cout << "~" << endl;
        }
    }
}


void Editor::draw_welcome_message(){
    stringstream ss_welcomeMessage;
    ss_welcomeMessage << "Ryan's Cecto editor -- version " << VERSION << "\r";
    int width = _terminal.Size().Width;
    int len = ss_welcomeMessage.str().length();
    int padding = (width - len ) / 2;
    cout << "~";
    for (int i=0;i< padding - 1;i++){
        cout << " ";
    }
    cout << ss_welcomeMessage.str() << endl;
}


//x和y为啥是反的？？？？上下改的是x？？？坐标系？？？
//_current_position.x:垂直方向
//_current_position.y:水平方向
void Editor::move_cursor(KeyCode keycode){
    
    TerminalSize size = _terminal.Size();
    int terminal_height = size.Height;
    int height = _document.Len();
    int width =0 ;

    int y = _cursor_position.x;
    int x = _cursor_position.y;

    Row* row = _document.GetRowByIndex(y);
    if (row == NULL){
        width =0;
    } else {
        width = row->Length();
    }

    switch(keycode.Type){
        case KeyType::Up:{
            y = Util::SaturatingSub(y,1);
        }break;
        case KeyType::Down:{
            if (y < height)
                y = Util::SaturatingAdd(y,1);
        }break;
        case KeyType::Left:{
            if (x > 0){
                x-=1;
            } else if (y>0){
                y -= 1;
                Row* row = _document.GetRowByIndex(y);
                if (row ==NULL){
                    x = 0;
                }else {
                    x = row->Length();
                }
            }
        }break;
        case KeyType::Right:{
            if (x < width){
                x += 1;
            } else if (y < height){
                y+=1;
                x = 0;
            }

        }break;
        case KeyType::PageUp:{
            if (y > terminal_height){
                y = y - terminal_height;
            }else {
                y = 0;
            }
        }break;
        case KeyType::PageDown:{
            if (Util::SaturatingAdd(y,terminal_height) < height) {
                y = y + terminal_height;
            }else {
                y=height;
            }
        }break;
        case KeyType::Home:{
                x=0;
        }break;
        case KeyType::End:{
                x=width;
        }break;
        default:{
            return;
        }
    }
    row = _document.GetRowByIndex(y);
    if (row == NULL){
        width =0;
    } else {
        width = row->Length();
    }
    if (x > width){
        x = width;
    }
    _cursor_position.x = y;
    _cursor_position.y = x;
}


void Editor::draw_row(Row *row){
    int width = _terminal.Size().Width;
    int start = _offset.y;
    int end = _offset.y+width;
    std::string rowStr = row->Render(start,end);
    cout << rowStr << endl;
}

void Editor::search(){
    Position old_pos =Position{ this->_cursor_position.x,this->_cursor_position.y};
    Editor::s_direction = SearchDirection::Forward;
    string rtn = this->Prompt("Search(ESC to cancel, Arrows to navigate): ",this->searchCallback);
    if (rtn.length() < 0){
        this->_cursor_position = old_pos;
        this->Scroll();
    }
    this->_highlighted_word = NONE_STRING;
    //if (rtn.length()>0){
    //    Position pos = this->_document.Find(rtn,old_pos);
    //    if (pos.x >=0 && pos.y >= 0){
    //        this->_cursor_position = pos;
    //        this->Scroll();
    //    } else {
    //        stringstream ss;
    //        ss<<"Not found :"<<rtn<<".";
    //        this->_status_message->SetMessage(ss.str());
    //    }
    //} else{
    //    this->_cursor_position = old_pos;
    //    this->Scroll();

    //}


}
